1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package com.sun.security.sasl.digest;
27
28 import java.util.Map;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.logging.Logger;
33 import java.util.logging.Level;
34 import java.math.BigInteger;
35 import java.util.Random;
36 import java.security.Provider;
37
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.UnsupportedEncodingException;
41 import java.io.IOException;
42
43 import java.security.MessageDigest;
44 import java.security.AccessController;
45 import java.security.PrivilegedAction;
46 import java.security.NoSuchAlgorithmException;
47 import java.security.InvalidKeyException;
48 import java.security.spec.KeySpec;
49 import java.security.spec.InvalidKeySpecException;
50 import java.security.InvalidAlgorithmParameterException;
51
52 import javax.crypto.Cipher;
53 import javax.crypto.SecretKey;
54 import javax.crypto.Mac;
55 import javax.crypto.SecretKeyFactory;
56 import javax.crypto.BadPaddingException;
57 import javax.crypto.NoSuchPaddingException;
58 import javax.crypto.IllegalBlockSizeException;
59 import javax.crypto.spec.IvParameterSpec;
60 import javax.crypto.spec.SecretKeySpec;
61 import javax.crypto.spec.DESKeySpec;
62 import javax.crypto.spec.DESedeKeySpec;
63
64 import javax.security.sasl.*;
65 import com.sun.security.sasl.util.AbstractSaslImpl;
66
67 import javax.security.auth.callback.CallbackHandler;
68
69
70
71
72
73
74
75
76
77
78
79 abstract class DigestMD5Base extends AbstractSaslImpl {
80
81
82
83 private static final String DI_CLASS_NAME = DigestIntegrity.class.getName();
84 private static final String DP_CLASS_NAME = DigestPrivacy.class.getName();
85
86
87 protected static final int MAX_CHALLENGE_LENGTH = 2048;
88 protected static final int MAX_RESPONSE_LENGTH = 4096;
89 protected static final int DEFAULT_MAXBUF = 65536;
90
91
92 protected static final int DES3 = 0;
93 protected static final int RC4 = 1;
94 protected static final int DES = 2;
95 protected static final int RC4_56 = 3;
96 protected static final int RC4_40 = 4;
97 protected static final String[] CIPHER_TOKENS = { "3des",
98 "rc4",
99 "des",
100 "rc4-56",
101 "rc4-40" };
102 private static final String[] JCE_CIPHER_NAME = {
103 "DESede/CBC/NoPadding",
104 "RC4",
105 "DES/CBC/NoPadding",
106 };
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 protected static final byte DES_3_STRENGTH = HIGH_STRENGTH;
125 protected static final byte RC4_STRENGTH = HIGH_STRENGTH;
126 protected static final byte DES_STRENGTH = MEDIUM_STRENGTH;
127 protected static final byte RC4_56_STRENGTH = MEDIUM_STRENGTH;
128 protected static final byte RC4_40_STRENGTH = LOW_STRENGTH;
129 protected static final byte UNSET = (byte)0;
130 protected static final byte[] CIPHER_MASKS = { DES_3_STRENGTH,
131 RC4_STRENGTH,
132 DES_STRENGTH,
133 RC4_56_STRENGTH,
134 RC4_40_STRENGTH };
135
136 private static final String SECURITY_LAYER_MARKER =
137 ":00000000000000000000000000000000";
138
139 protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
140
141
142
143
144 protected int step;
145
146
147
148 protected CallbackHandler cbh;
149
150 protected SecurityCtx secCtx;
151 protected byte[] H_A1;
152
153 protected byte[] nonce;
154
155
156 protected String negotiatedStrength;
157 protected String negotiatedCipher;
158 protected String negotiatedQop;
159 protected String negotiatedRealm;
160 protected boolean useUTF8 = false;
161 protected String encoding = "8859_1";
162
163 protected String digestUri;
164 protected String authzid;
165
166
167
168
169
170
171
172
173
174
175
176
177
178 protected DigestMD5Base(Map props, String className, int firstStep,
179 String digestUri, CallbackHandler cbh) throws SaslException {
180 super(props, className);
181
182 step = firstStep;
183 this.digestUri = digestUri;
184 this.cbh = cbh;
185 }
186
187
188
189
190
191
192 public String getMechanismName() {
193 return "DIGEST-MD5";
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException {
209 if (!completed) {
210 throw new IllegalStateException(
211 "DIGEST-MD5 authentication not completed");
212 }
213
214 if (secCtx == null) {
215 throw new IllegalStateException(
216 "Neither integrity nor privacy was negotiated");
217 }
218
219 return (secCtx.unwrap(incoming, start, len));
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234 public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException {
235 if (!completed) {
236 throw new IllegalStateException(
237 "DIGEST-MD5 authentication not completed");
238 }
239
240 if (secCtx == null) {
241 throw new IllegalStateException(
242 "Neither integrity nor privacy was negotiated");
243 }
244
245 return (secCtx.wrap(outgoing, start, len));
246 }
247
248 public void dispose() throws SaslException {
249 if (secCtx != null) {
250 secCtx = null;
251 }
252 }
253
254 public Object getNegotiatedProperty(String propName) {
255 if (completed) {
256 if (propName.equals(Sasl.STRENGTH)) {
257 return negotiatedStrength;
258 } else {
259 return super.getNegotiatedProperty(propName);
260 }
261 } else {
262 throw new IllegalStateException(
263 "DIGEST-MD5 authentication not completed");
264 }
265 }
266
267
268
269
270
271
272
273
274
275
276
277
278
279 private final static char pem_array[] = {
280
281 'A','B','C','D','E','F','G','H',
282 'I','J','K','L','M','N','O','P',
283 'Q','R','S','T','U','V','W','X',
284 'Y','Z','a','b','c','d','e','f',
285 'g','h','i','j','k','l','m','n',
286 'o','p','q','r','s','t','u','v',
287 'w','x','y','z','0','1','2','3',
288 '4','5','6','7','8','9','+','/'
289 };
290
291
292 private static final int RAW_NONCE_SIZE = 30;
293
294
295 private static final int ENCODED_NONCE_SIZE = RAW_NONCE_SIZE*4/3;
296
297 protected static final byte[] generateNonce() {
298
299
300 Random random = new Random();
301 byte[] randomData = new byte[RAW_NONCE_SIZE];
302 random.nextBytes(randomData);
303
304 byte[] nonce = new byte[ENCODED_NONCE_SIZE];
305
306
307 byte a, b, c;
308 int j = 0;
309 for (int i = 0; i < randomData.length; i += 3) {
310 a = randomData[i];
311 b = randomData[i+1];
312 c = randomData[i+2];
313 nonce[j++] = (byte)(pem_array[(a >>> 2) & 0x3F]);
314 nonce[j++] = (byte)(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
315 nonce[j++] = (byte)(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
316 nonce[j++] = (byte)(pem_array[c & 0x3F]);
317 }
318
319 return nonce;
320
321
322
323
324 }
325
326
327
328
329
330 protected static void writeQuotedStringValue(ByteArrayOutputStream out,
331 byte[] buf) {
332
333 int len = buf.length;
334 byte ch;
335 for (int i = 0; i < len; i++) {
336 ch = buf[i];
337 if (needEscape((char)ch)) {
338 out.write('\\');
339 }
340 out.write(ch);
341 }
342 }
343
344
345
346 private static boolean needEscape(String str) {
347 int len = str.length();
348 for (int i = 0; i < len; i++) {
349 if (needEscape(str.charAt(i))) {
350 return true;
351 }
352 }
353 return false;
354 }
355
356
357 private static boolean needEscape(char ch) {
358 return ch == '"' ||
359 ch == '\\' ||
360 ch == 127 ||
361
362
363 (ch >= 0 && ch <= 31 && ch != 13 && ch != 9 && ch != 10);
364 }
365
366 protected static String quotedStringValue(String str) {
367 if (needEscape(str)) {
368 int len = str.length();
369 char[] buf = new char[len+len];
370 int j = 0;
371 char ch;
372 for (int i = 0; i < len; i++) {
373 ch = str.charAt(i);
374 if (needEscape(ch)) {
375 buf[j++] = '\\';
376 }
377 buf[j++] = ch;
378 }
379 return new String(buf, 0, j);
380 } else {
381 return str;
382 }
383 }
384
385
386
387
388
389
390
391 protected byte[] binaryToHex(byte[] digest) throws
392 UnsupportedEncodingException {
393
394 StringBuffer digestString = new StringBuffer();
395
396 for (int i = 0; i < digest.length; i ++) {
397 if ((digest[i] & 0x000000ff) < 0x10) {
398 digestString.append("0"+
399 Integer.toHexString(digest[i] & 0x000000ff));
400 } else {
401 digestString.append(
402 Integer.toHexString(digest[i] & 0x000000ff));
403 }
404 }
405 return digestString.toString().getBytes(encoding);
406 }
407
408
409
410
411
412
413
414
415
416 protected byte[] stringToByte_8859_1(String str) throws SaslException {
417
418 char[] buffer = str.toCharArray();
419
420 try {
421 if (useUTF8) {
422 for( int i = 0; i< buffer.length; i++ ) {
423 if( buffer[i] > '\u00FF' ) {
424 return str.getBytes("UTF8");
425 }
426 }
427 }
428 return str.getBytes("8859_1");
429 } catch (UnsupportedEncodingException e) {
430 throw new SaslException(
431 "cannot encode string in UTF8 or 8859-1 (Latin-1)", e);
432 }
433 }
434
435 protected static byte[] getPlatformCiphers() {
436 byte[] ciphers = new byte[CIPHER_TOKENS.length];
437
438 for (int i = 0; i < JCE_CIPHER_NAME.length; i++) {
439 try {
440
441
442 Cipher.getInstance(JCE_CIPHER_NAME[i]);
443
444 logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]);
445 ciphers[i] |= CIPHER_MASKS[i];
446 } catch (NoSuchAlgorithmException e) {
447
448 } catch (NoSuchPaddingException e) {
449
450 }
451 }
452
453 if (ciphers[RC4] != UNSET) {
454 ciphers[RC4_56] |= CIPHER_MASKS[RC4_56];
455 ciphers[RC4_40] |= CIPHER_MASKS[RC4_40];
456 }
457
458 return ciphers;
459 }
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474 protected byte[] generateResponseValue(
475 String authMethod,
476 String digestUriValue,
477 String qopValue,
478 String usernameValue,
479 String realmValue,
480 char[] passwdValue,
481 byte[] nonceValue,
482 byte[] cNonceValue,
483 int nonceCount,
484 byte[] authzidValue
485 ) throws NoSuchAlgorithmException,
486 UnsupportedEncodingException,
487 IOException {
488
489 MessageDigest md5 = MessageDigest.getInstance("MD5");
490 byte[] hexA1, hexA2;
491 ByteArrayOutputStream A2, beginA1, A1, KD;
492
493
494
495
496
497
498 A2 = new ByteArrayOutputStream();
499 A2.write((authMethod + ":" + digestUriValue).getBytes(encoding));
500 if (qopValue.equals("auth-conf") ||
501 qopValue.equals("auth-int")) {
502
503 logger.log(Level.FINE, "DIGEST04:QOP: {0}", qopValue);
504
505 A2.write(SECURITY_LAYER_MARKER.getBytes(encoding));
506 }
507
508 if (logger.isLoggable(Level.FINE)) {
509 logger.log(Level.FINE, "DIGEST05:A2: {0}", A2.toString());
510 }
511
512 md5.update(A2.toByteArray());
513 byte[] digest = md5.digest();
514 hexA2 = binaryToHex(digest);
515
516 if (logger.isLoggable(Level.FINE)) {
517 logger.log(Level.FINE, "DIGEST06:HEX(H(A2)): {0}", new String(hexA2));
518 }
519
520
521
522
523
524 beginA1 = new ByteArrayOutputStream();
525 beginA1.write(stringToByte_8859_1(usernameValue));
526 beginA1.write(':');
527
528 beginA1.write(stringToByte_8859_1(realmValue));
529 beginA1.write(':');
530 beginA1.write(stringToByte_8859_1(new String(passwdValue)));
531
532 md5.update(beginA1.toByteArray());
533 digest = md5.digest();
534
535 if (logger.isLoggable(Level.FINE)) {
536 logger.log(Level.FINE, "DIGEST07:H({0}) = {1}",
537 new Object[]{beginA1.toString(), new String(binaryToHex(digest))});
538 }
539
540
541
542
543
544
545 A1 = new ByteArrayOutputStream();
546 A1.write(digest);
547 A1.write(':');
548 A1.write(nonceValue);
549 A1.write(':');
550 A1.write(cNonceValue);
551
552 if (authzidValue != null) {
553 A1.write(':');
554 A1.write(authzidValue);
555 }
556 md5.update(A1.toByteArray());
557 digest = md5.digest();
558 H_A1 = digest;
559 hexA1 = binaryToHex(digest);
560
561 if (logger.isLoggable(Level.FINE)) {
562 logger.log(Level.FINE, "DIGEST08:H(A1) = {0}", new String(hexA1));
563 }
564
565
566
567
568 KD = new ByteArrayOutputStream();
569 KD.write(hexA1);
570 KD.write(':');
571 KD.write(nonceValue);
572 KD.write(':');
573 KD.write(nonceCountToHex(nonceCount).getBytes(encoding));
574 KD.write(':');
575 KD.write(cNonceValue);
576 KD.write(':');
577 KD.write(qopValue.getBytes(encoding));
578 KD.write(':');
579 KD.write(hexA2);
580
581 if (logger.isLoggable(Level.FINE)) {
582 logger.log(Level.FINE, "DIGEST09:KD: {0}", KD.toString());
583 }
584
585 md5.update(KD.toByteArray());
586 digest = md5.digest();
587
588 byte[] answer = binaryToHex(digest);
589
590 if (logger.isLoggable(Level.FINE)) {
591 logger.log(Level.FINE, "DIGEST10:response-value: {0}",
592 new String(answer));
593 }
594 return (answer);
595 }
596
597
598
599
600
601
602 protected static String nonceCountToHex(int count) {
603
604 String str = Integer.toHexString(count);
605 StringBuffer pad = new StringBuffer();
606
607 if (str.length() < 8) {
608 for (int i = 0; i < 8-str.length(); i ++) {
609 pad.append("0");
610 }
611 }
612
613 return pad.toString() + str;
614 }
615
616
617
618
619
620
621
622
623
624
625 protected static byte[][] parseDirectives(byte[] buf,
626 String[]keyTable, List<byte[]> realmChoices, int realmIndex) throws SaslException {
627
628 byte[][] valueTable = new byte[keyTable.length][];
629
630 ByteArrayOutputStream key = new ByteArrayOutputStream(10);
631 ByteArrayOutputStream value = new ByteArrayOutputStream(10);
632 boolean gettingKey = true;
633 boolean gettingQuotedValue = false;
634 boolean expectSeparator = false;
635 byte bch;
636
637 int i = skipLws(buf, 0);
638 while (i < buf.length) {
639 bch = buf[i];
640
641 if (gettingKey) {
642 if (bch == ',') {
643 if (key.size() != 0) {
644 throw new SaslException("Directive key contains a ',':" +
645 key);
646 }
647
648 i = skipLws(buf, i+1);
649
650 } else if (bch == '=') {
651 if (key.size() == 0) {
652 throw new SaslException("Empty directive key");
653 }
654 gettingKey = false;
655 i = skipLws(buf, i+1);
656
657
658 if (i < buf.length) {
659 if (buf[i] == '"') {
660 gettingQuotedValue = true;
661 ++i;
662 }
663 } else {
664 throw new SaslException(
665 "Valueless directive found: " + key.toString());
666 }
667 } else if (isLws(bch)) {
668
669 i = skipLws(buf, i+1);
670
671
672 if (i < buf.length) {
673 if (buf[i] != '=') {
674 throw new SaslException("'=' expected after key: " +
675 key.toString());
676 }
677 } else {
678 throw new SaslException(
679 "'=' expected after key: " + key.toString());
680 }
681 } else {
682 key.write(bch);
683 ++i;
684 }
685 } else if (gettingQuotedValue) {
686
687 if (bch == '\\') {
688
689 ++i;
690 if (i < buf.length) {
691 value.write(buf[i]);
692 ++i;
693 } else {
694
695 throw new SaslException(
696 "Unmatched quote found for directive: "
697 + key.toString() + " with value: " + value.toString());
698 }
699 } else if (bch == '"') {
700
701 ++i;
702 gettingQuotedValue = false;
703 expectSeparator = true;
704 } else {
705 value.write(bch);
706 ++i;
707 }
708
709 } else if (isLws(bch) || bch == ',') {
710
711
712 extractDirective(key.toString(), value.toByteArray(),
713 keyTable, valueTable, realmChoices, realmIndex);
714 key.reset();
715 value.reset();
716 gettingKey = true;
717 gettingQuotedValue = expectSeparator = false;
718 i = skipLws(buf, i+1);
719
720 } else if (expectSeparator) {
721 throw new SaslException(
722 "Expecting comma or linear whitespace after quoted string: \""
723 + value.toString() + "\"");
724 } else {
725 value.write(bch);
726 ++i;
727 }
728 }
729
730 if (gettingQuotedValue) {
731 throw new SaslException(
732 "Unmatched quote found for directive: " + key.toString() +
733 " with value: " + value.toString());
734 }
735
736
737 if (key.size() > 0) {
738 extractDirective(key.toString(), value.toByteArray(),
739 keyTable, valueTable, realmChoices, realmIndex);
740 }
741
742 return valueTable;
743 }
744
745
746
747
748 private static boolean isLws(byte b) {
749 switch (b) {
750 case 13:
751 case 10:
752 case 32:
753 case 9:
754 return true;
755 }
756 return false;
757 }
758
759
760 private static int skipLws(byte[] buf, int start) {
761 int i;
762 for (i = start; i < buf.length; i++) {
763 if (!isLws(buf[i])) {
764 return i;
765 }
766 }
767 return i;
768 }
769
770
771
772
773
774
775
776
777
778 private static void extractDirective(String key, byte[] value,
779 String[] keyTable, byte[][] valueTable,
780 List<byte[]> realmChoices, int realmIndex) throws SaslException {
781
782 for (int i = 0; i < keyTable.length; i++) {
783 if (key.equalsIgnoreCase(keyTable[i])) {
784 if (valueTable[i] == null) {
785 valueTable[i] = value;
786 if (logger.isLoggable(Level.FINE)) {
787 logger.log(Level.FINE, "DIGEST11:Directive {0} = {1}",
788 new Object[]{
789 keyTable[i],
790 new String(valueTable[i])});
791 }
792 } else if (realmChoices != null && i == realmIndex) {
793
794 if (realmChoices.size() == 0) {
795 realmChoices.add(valueTable[i]);
796 }
797 realmChoices.add(value);
798 } else {
799 throw new SaslException(
800 "DIGEST-MD5: peer sent more than one " +
801 key + " directive: " + new String(value));
802 }
803
804 break;
805 }
806 }
807 }
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822 class DigestIntegrity implements SecurityCtx {
823
824 static final private String CLIENT_INT_MAGIC = "Digest session key to " +
825 "client-to-server signing key magic constant";
826 static final private String SVR_INT_MAGIC = "Digest session key to " +
827 "server-to-client signing key magic constant";
828
829
830 protected byte[] myKi;
831 protected byte[] peerKi;
832
833 protected int mySeqNum = 0;
834 protected int peerSeqNum = 0;
835
836
837 protected final byte[] messageType = new byte[2];
838 protected final byte[] sequenceNum = new byte[4];
839
840
841
842
843
844
845
846
847 DigestIntegrity(boolean clientMode) throws SaslException {
848
849
850 try {
851 generateIntegrityKeyPair(clientMode);
852
853 } catch (UnsupportedEncodingException e) {
854 throw new SaslException(
855 "DIGEST-MD5: Error encoding strings into UTF-8", e);
856
857 } catch (IOException e) {
858 throw new SaslException("DIGEST-MD5: Error accessing buffers " +
859 "required to create integrity key pairs", e);
860
861 } catch (NoSuchAlgorithmException e) {
862 throw new SaslException("DIGEST-MD5: Unsupported digest " +
863 "algorithm used to create integrity key pairs", e);
864 }
865
866
867 intToNetworkByteOrder(1, messageType, 0, 2);
868 }
869
870
871
872
873
874
875
876
877
878
879
880
881 private void generateIntegrityKeyPair(boolean clientMode)
882 throws UnsupportedEncodingException, IOException,
883 NoSuchAlgorithmException {
884
885 byte[] cimagic = CLIENT_INT_MAGIC.getBytes(encoding);
886 byte[] simagic = SVR_INT_MAGIC.getBytes(encoding);
887
888 MessageDigest md5 = MessageDigest.getInstance("MD5");
889
890
891 byte[] keyBuffer = new byte[H_A1.length + cimagic.length];
892
893
894 System.arraycopy(H_A1, 0, keyBuffer, 0, H_A1.length);
895 System.arraycopy(cimagic, 0, keyBuffer, H_A1.length, cimagic.length);
896 md5.update(keyBuffer);
897 byte[] Kic = md5.digest();
898
899
900
901 System.arraycopy(simagic, 0, keyBuffer, H_A1.length, simagic.length);
902
903 md5.update(keyBuffer);
904 byte[] Kis = md5.digest();
905
906 if (logger.isLoggable(Level.FINER)) {
907 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
908 "DIGEST12:Kic: ", Kic);
909 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
910 "DIGEST13:Kis: ", Kis);
911 }
912
913 if (clientMode) {
914 myKi = Kic;
915 peerKi = Kis;
916 } else {
917 myKi = Kis;
918 peerKi = Kic;
919 }
920 }
921
922
923
924
925
926
927
928
929
930
931
932
933
934 public byte[] wrap(byte[] outgoing, int start, int len)
935 throws SaslException {
936
937 if (len == 0) {
938 return EMPTY_BYTE_ARRAY;
939 }
940
941
942 byte[] wrapped = new byte[len+10+2+4];
943
944
945 System.arraycopy(outgoing, start, wrapped, 0, len);
946
947 incrementSeqNum();
948
949
950 byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
951
952 if (logger.isLoggable(Level.FINEST)) {
953 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST14:outgoing: ",
954 outgoing, start, len);
955 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST15:seqNum: ",
956 sequenceNum);
957 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST16:MAC: ", mac);
958 }
959
960
961 System.arraycopy(mac, 0, wrapped, len, 10);
962
963
964 System.arraycopy(messageType, 0, wrapped, len+10, 2);
965
966
967 System.arraycopy(sequenceNum, 0, wrapped, len+12, 4);
968 if (logger.isLoggable(Level.FINEST)) {
969 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST17:wrapped: ", wrapped);
970 }
971 return wrapped;
972 }
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989 public byte[] unwrap(byte[] incoming, int start, int len)
990 throws SaslException {
991
992 if (len == 0) {
993 return EMPTY_BYTE_ARRAY;
994 }
995
996
997 byte[] mac = new byte[10];
998 byte[] msg = new byte[len - 16];
999 byte[] msgType = new byte[2];
1000 byte[] seqNum = new byte[4];
1001
1002
1003 System.arraycopy(incoming, start, msg, 0, msg.length);
1004 System.arraycopy(incoming, start+msg.length, mac, 0, 10);
1005 System.arraycopy(incoming, start+msg.length+10, msgType, 0, 2);
1006 System.arraycopy(incoming, start+msg.length+12, seqNum, 0, 4);
1007
1008
1009 byte[] expectedMac = getHMAC(peerKi, seqNum, msg, 0, msg.length);
1010
1011 if (logger.isLoggable(Level.FINEST)) {
1012 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST18:incoming: ",
1013 msg);
1014 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST19:MAC: ",
1015 mac);
1016 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST20:messageType: ",
1017 msgType);
1018 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST21:sequenceNum: ",
1019 seqNum);
1020 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST22:expectedMAC: ",
1021 expectedMac);
1022 }
1023
1024
1025 if (!Arrays.equals(mac, expectedMac)) {
1026
1027 logger.log(Level.INFO, "DIGEST23:Unmatched MACs");
1028 return EMPTY_BYTE_ARRAY;
1029 }
1030
1031
1032 if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
1033 throw new SaslException("DIGEST-MD5: Out of order " +
1034 "sequencing of messages from server. Got: " +
1035 networkByteOrderToInt(seqNum, 0, 4) +
1036 " Expected: " + peerSeqNum);
1037 }
1038
1039 if (!Arrays.equals(messageType, msgType)) {
1040 throw new SaslException("DIGEST-MD5: invalid message type: " +
1041 networkByteOrderToInt(msgType, 0, 2));
1042 }
1043
1044
1045 peerSeqNum++;
1046 return msg;
1047 }
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061 protected byte[] getHMAC(byte[] Ki, byte[] seqnum, byte[] msg,
1062 int start, int len) throws SaslException {
1063
1064 byte[] seqAndMsg = new byte[4+len];
1065 System.arraycopy(seqnum, 0, seqAndMsg, 0, 4);
1066 System.arraycopy(msg, start, seqAndMsg, 4, len);
1067
1068 try {
1069 SecretKey keyKi = new SecretKeySpec(Ki, "HmacMD5");
1070 Mac m = Mac.getInstance("HmacMD5");
1071 m.init(keyKi);
1072 m.update(seqAndMsg);
1073 byte[] hMAC_MD5 = m.doFinal();
1074
1075
1076 byte macBuffer[] = new byte[10];
1077 System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10);
1078
1079 return macBuffer;
1080 } catch (InvalidKeyException e) {
1081 throw new SaslException("DIGEST-MD5: Invalid bytes used for " +
1082 "key of HMAC-MD5 hash.", e);
1083 } catch (NoSuchAlgorithmException e) {
1084 throw new SaslException("DIGEST-MD5: Error creating " +
1085 "instance of MD5 digest algorithm", e);
1086 }
1087 }
1088
1089
1090
1091
1092 protected void incrementSeqNum() {
1093 intToNetworkByteOrder(mySeqNum++, sequenceNum, 0, 4);
1094 }
1095 }
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109 final class DigestPrivacy extends DigestIntegrity implements SecurityCtx {
1110
1111 static final private String CLIENT_CONF_MAGIC =
1112 "Digest H(A1) to client-to-server sealing key magic constant";
1113 static final private String SVR_CONF_MAGIC =
1114 "Digest H(A1) to server-to-client sealing key magic constant";
1115
1116 private Cipher encCipher;
1117 private Cipher decCipher;
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 DigestPrivacy(boolean clientMode) throws SaslException {
1130
1131 super(clientMode);
1132
1133 try {
1134 generatePrivacyKeyPair(clientMode);
1135
1136 } catch (SaslException e) {
1137 throw e;
1138
1139 } catch (UnsupportedEncodingException e) {
1140 throw new SaslException(
1141 "DIGEST-MD5: Error encoding string value into UTF-8", e);
1142
1143 } catch (IOException e) {
1144 throw new SaslException("DIGEST-MD5: Error accessing " +
1145 "buffers required to generate cipher keys", e);
1146 } catch (NoSuchAlgorithmException e) {
1147 throw new SaslException("DIGEST-MD5: Error creating " +
1148 "instance of required cipher or digest", e);
1149 }
1150 }
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165 private void generatePrivacyKeyPair(boolean clientMode)
1166 throws IOException, UnsupportedEncodingException,
1167 NoSuchAlgorithmException, SaslException {
1168
1169 byte[] ccmagic = CLIENT_CONF_MAGIC.getBytes(encoding);
1170 byte[] scmagic = SVR_CONF_MAGIC.getBytes(encoding);
1171
1172
1173 MessageDigest md5 = MessageDigest.getInstance("MD5");
1174
1175 int n;
1176 if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_40])) {
1177 n = 5;
1178 } else if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_56])) {
1179 n = 7;
1180 } else {
1181 n = 16;
1182 }
1183
1184
1185
1186 byte[] keyBuffer = new byte[n + ccmagic.length];
1187 System.arraycopy(H_A1, 0, keyBuffer, 0, n);
1188
1189
1190 System.arraycopy(ccmagic, 0, keyBuffer, n, ccmagic.length);
1191 md5.update(keyBuffer);
1192 byte[] Kcc = md5.digest();
1193
1194
1195
1196 System.arraycopy(scmagic, 0, keyBuffer, n, scmagic.length);
1197 md5.update(keyBuffer);
1198 byte[] Kcs = md5.digest();
1199
1200 if (logger.isLoggable(Level.FINER)) {
1201 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1202 "DIGEST24:Kcc: ", Kcc);
1203 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1204 "DIGEST25:Kcs: ", Kcs);
1205 }
1206
1207 byte[] myKc;
1208 byte[] peerKc;
1209
1210 if (clientMode) {
1211 myKc = Kcc;
1212 peerKc = Kcs;
1213 } else {
1214 myKc = Kcs;
1215 peerKc = Kcc;
1216 }
1217
1218 try {
1219 SecretKey encKey;
1220 SecretKey decKey;
1221
1222
1223 if (negotiatedCipher.indexOf(CIPHER_TOKENS[RC4]) > -1) {
1224 encCipher = Cipher.getInstance("RC4");
1225 decCipher = Cipher.getInstance("RC4");
1226
1227 encKey = new SecretKeySpec(myKc, "RC4");
1228 decKey = new SecretKeySpec(peerKc, "RC4");
1229
1230 encCipher.init(Cipher.ENCRYPT_MODE, encKey);
1231 decCipher.init(Cipher.DECRYPT_MODE, decKey);
1232
1233 } else if ((negotiatedCipher.equals(CIPHER_TOKENS[DES])) ||
1234 (negotiatedCipher.equals(CIPHER_TOKENS[DES3]))) {
1235
1236
1237 String cipherFullname, cipherShortname;
1238
1239
1240
1241
1242 if (negotiatedCipher.equals(CIPHER_TOKENS[DES])) {
1243 cipherFullname = "DES/CBC/NoPadding";
1244 cipherShortname = "des";
1245 } else {
1246
1247 cipherFullname = "DESede/CBC/NoPadding";
1248 cipherShortname = "desede";
1249 }
1250
1251 encCipher = Cipher.getInstance(cipherFullname);
1252 decCipher = Cipher.getInstance(cipherFullname);
1253
1254 encKey = makeDesKeys(myKc, cipherShortname);
1255 decKey = makeDesKeys(peerKc, cipherShortname);
1256
1257
1258 IvParameterSpec encIv = new IvParameterSpec(myKc, 8, 8);
1259 IvParameterSpec decIv = new IvParameterSpec(peerKc, 8, 8);
1260
1261
1262 encCipher.init(Cipher.ENCRYPT_MODE, encKey, encIv);
1263 decCipher.init(Cipher.DECRYPT_MODE, decKey, decIv);
1264
1265 if (logger.isLoggable(Level.FINER)) {
1266 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1267 "DIGEST26:" + negotiatedCipher + " IVcc: ",
1268 encIv.getIV());
1269 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1270 "DIGEST27:" + negotiatedCipher + " IVcs: ",
1271 decIv.getIV());
1272 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1273 "DIGEST28:" + negotiatedCipher + " encryption key: ",
1274 encKey.getEncoded());
1275 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1276 "DIGEST29:" + negotiatedCipher + " decryption key: ",
1277 decKey.getEncoded());
1278 }
1279 }
1280 } catch (InvalidKeySpecException e) {
1281 throw new SaslException("DIGEST-MD5: Unsupported key " +
1282 "specification used.", e);
1283 } catch (InvalidAlgorithmParameterException e) {
1284 throw new SaslException("DIGEST-MD5: Invalid cipher " +
1285 "algorithem parameter used to create cipher instance", e);
1286 } catch (NoSuchPaddingException e) {
1287 throw new SaslException("DIGEST-MD5: Unsupported " +
1288 "padding used for chosen cipher", e);
1289 } catch (InvalidKeyException e) {
1290 throw new SaslException("DIGEST-MD5: Invalid data " +
1291 "used to initialize keys", e);
1292 }
1293 }
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310 public byte[] wrap(byte[] outgoing, int start, int len)
1311 throws SaslException {
1312
1313 if (len == 0) {
1314 return EMPTY_BYTE_ARRAY;
1315 }
1316
1317
1318 incrementSeqNum();
1319 byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
1320
1321 if (logger.isLoggable(Level.FINEST)) {
1322 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST30:Outgoing: ",
1323 outgoing, start, len);
1324 traceOutput(DP_CLASS_NAME, "wrap", "seqNum: ",
1325 sequenceNum);
1326 traceOutput(DP_CLASS_NAME, "wrap", "MAC: ", mac);
1327 }
1328
1329
1330 int bs = encCipher.getBlockSize();
1331 byte[] padding;
1332 if (bs > 1 ) {
1333 int pad = bs - ((len + 10) % bs);
1334 padding = new byte[pad];
1335 for (int i=0; i < pad; i++) {
1336 padding[i] = (byte)pad;
1337 }
1338 } else {
1339 padding = EMPTY_BYTE_ARRAY;
1340 }
1341
1342 byte[] toBeEncrypted = new byte[len+padding.length+10];
1343
1344
1345 System.arraycopy(outgoing, start, toBeEncrypted, 0, len);
1346 System.arraycopy(padding, 0, toBeEncrypted, len, padding.length);
1347 System.arraycopy(mac, 0, toBeEncrypted, len+padding.length, 10);
1348
1349 if (logger.isLoggable(Level.FINEST)) {
1350 traceOutput(DP_CLASS_NAME, "wrap",
1351 "DIGEST31:{msg, pad, KicMAC}: ", toBeEncrypted);
1352 }
1353
1354
1355 byte[] cipherBlock;
1356 try {
1357
1358 cipherBlock = encCipher.update(toBeEncrypted);
1359
1360 if (cipherBlock == null) {
1361
1362 throw new IllegalBlockSizeException(""+toBeEncrypted.length);
1363 }
1364 } catch (IllegalBlockSizeException e) {
1365 throw new SaslException(
1366 "DIGEST-MD5: Invalid block size for cipher", e);
1367 }
1368
1369 byte[] wrapped = new byte[cipherBlock.length+2+4];
1370 System.arraycopy(cipherBlock, 0, wrapped, 0, cipherBlock.length);
1371 System.arraycopy(messageType, 0, wrapped, cipherBlock.length, 2);
1372 System.arraycopy(sequenceNum, 0, wrapped, cipherBlock.length+2, 4);
1373
1374 if (logger.isLoggable(Level.FINEST)) {
1375 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST32:Wrapped: ", wrapped);
1376 }
1377
1378 return wrapped;
1379 }
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395 public byte[] unwrap(byte[] incoming, int start, int len)
1396 throws SaslException {
1397
1398 if (len == 0) {
1399 return EMPTY_BYTE_ARRAY;
1400 }
1401
1402 byte[] encryptedMsg = new byte[len - 6];
1403 byte[] msgType = new byte[2];
1404 byte[] seqNum = new byte[4];
1405
1406
1407 System.arraycopy(incoming, start,
1408 encryptedMsg, 0, encryptedMsg.length);
1409 System.arraycopy(incoming, start+encryptedMsg.length,
1410 msgType, 0, 2);
1411 System.arraycopy(incoming, start+encryptedMsg.length+2,
1412 seqNum, 0, 4);
1413
1414 if (logger.isLoggable(Level.FINEST)) {
1415 logger.log(Level.FINEST,
1416 "DIGEST33:Expecting sequence num: {0}",
1417 new Integer(peerSeqNum));
1418 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST34:incoming: ",
1419 encryptedMsg);
1420 }
1421
1422
1423
1424 byte[] decryptedMsg;
1425
1426 try {
1427
1428 decryptedMsg = decCipher.update(encryptedMsg);
1429
1430 if (decryptedMsg == null) {
1431
1432 throw new IllegalBlockSizeException(""+encryptedMsg.length);
1433 }
1434 } catch (IllegalBlockSizeException e) {
1435 throw new SaslException("DIGEST-MD5: Illegal block " +
1436 "sizes used with chosen cipher", e);
1437 }
1438
1439 byte[] msgWithPadding = new byte[decryptedMsg.length - 10];
1440 byte[] mac = new byte[10];
1441
1442 System.arraycopy(decryptedMsg, 0,
1443 msgWithPadding, 0, msgWithPadding.length);
1444 System.arraycopy(decryptedMsg, msgWithPadding.length,
1445 mac, 0, 10);
1446
1447 if (logger.isLoggable(Level.FINEST)) {
1448 traceOutput(DP_CLASS_NAME, "unwrap",
1449 "DIGEST35:Unwrapped (w/padding): ", msgWithPadding);
1450 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST36:MAC: ", mac);
1451 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST37:messageType: ",
1452 msgType);
1453 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST38:sequenceNum: ",
1454 seqNum);
1455 }
1456
1457 int msgLength = msgWithPadding.length;
1458 int blockSize = decCipher.getBlockSize();
1459 if (blockSize > 1) {
1460
1461 msgLength -= (int)msgWithPadding[msgWithPadding.length - 1];
1462 if (msgLength < 0) {
1463
1464 if (logger.isLoggable(Level.INFO)) {
1465 logger.log(Level.INFO,
1466 "DIGEST39:Incorrect padding: {0}",
1467 new Byte(msgWithPadding[msgWithPadding.length - 1]));
1468 }
1469 return EMPTY_BYTE_ARRAY;
1470 }
1471 }
1472
1473
1474 byte[] expectedMac = getHMAC(peerKi, seqNum, msgWithPadding,
1475 0, msgLength);
1476
1477 if (logger.isLoggable(Level.FINEST)) {
1478 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST40:KisMAC: ",
1479 expectedMac);
1480 }
1481
1482
1483 if (!Arrays.equals(mac, expectedMac)) {
1484
1485 logger.log(Level.INFO, "DIGEST41:Unmatched MACs");
1486 return EMPTY_BYTE_ARRAY;
1487 }
1488
1489
1490 if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
1491 throw new SaslException("DIGEST-MD5: Out of order " +
1492 "sequencing of messages from server. Got: " +
1493 networkByteOrderToInt(seqNum, 0, 4) + " Expected: " +
1494 peerSeqNum);
1495 }
1496
1497
1498 if (!Arrays.equals(messageType, msgType)) {
1499 throw new SaslException("DIGEST-MD5: invalid message type: " +
1500 networkByteOrderToInt(msgType, 0, 2));
1501 }
1502
1503
1504 peerSeqNum++;
1505
1506 if (msgLength == msgWithPadding.length) {
1507 return msgWithPadding;
1508 } else {
1509
1510 byte[] clearMsg = new byte[msgLength];
1511 System.arraycopy(msgWithPadding, 0, clearMsg, 0, msgLength);
1512 return clearMsg;
1513 }
1514 }
1515 }
1516
1517
1518
1519 private static final BigInteger MASK = new BigInteger("7f", 16);
1520
1521
1522
1523
1524
1525 private static void setParityBit(byte[] key) {
1526 for (int i = 0; i < key.length; i++) {
1527 int b = key[i] & 0xfe;
1528 b |= (Integer.bitCount(b) & 1) ^ 1;
1529 key[i] = (byte) b;
1530 }
1531 }
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542 private static byte[] addDesParity(byte[] input, int offset, int len) {
1543 if (len != 7)
1544 throw new IllegalArgumentException(
1545 "Invalid length of DES Key Value:" + len);
1546
1547 byte[] raw = new byte[7];
1548 System.arraycopy(input, offset, raw, 0, len);
1549
1550 byte[] result = new byte[8];
1551 BigInteger in = new BigInteger(raw);
1552
1553
1554 for (int i=result.length-1; i>=0; i--) {
1555 result[i] = in.and(MASK).toByteArray()[0];
1556 result[i] <<= 1;
1557 in = in.shiftRight(7);
1558 }
1559 setParityBit(result);
1560 return result;
1561 }
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578 private static SecretKey makeDesKeys(byte[] input, String desStrength)
1579 throws NoSuchAlgorithmException, InvalidKeyException,
1580 InvalidKeySpecException {
1581
1582
1583 byte[] subkey1 = addDesParity(input, 0, 7);
1584
1585 KeySpec spec = null;
1586 SecretKeyFactory desFactory =
1587 SecretKeyFactory.getInstance(desStrength);
1588
1589 if (desStrength.equals("des")) {
1590 spec = new DESKeySpec(subkey1, 0);
1591 if (logger.isLoggable(Level.FINEST)) {
1592 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1593 "DIGEST42:DES key input: ", input);
1594 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1595 "DIGEST43:DES key parity-adjusted: ", subkey1);
1596 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1597 "DIGEST44:DES key material: ", ((DESKeySpec)spec).getKey());
1598 logger.log(Level.FINEST, "DIGEST45: is parity-adjusted? {0}",
1599 Boolean.valueOf(DESKeySpec.isParityAdjusted(subkey1, 0)));
1600 }
1601
1602 } else if (desStrength.equals("desede")) {
1603
1604
1605 byte[] subkey2 = addDesParity(input, 7, 7);
1606
1607
1608 byte[] ede = new byte[subkey1.length*2+subkey2.length];
1609 System.arraycopy(subkey1, 0, ede, 0, subkey1.length);
1610 System.arraycopy(subkey2, 0, ede, subkey1.length, subkey2.length);
1611 System.arraycopy(subkey1, 0, ede, subkey1.length+subkey2.length,
1612 subkey1.length);
1613
1614 spec = new DESedeKeySpec(ede, 0);
1615 if (logger.isLoggable(Level.FINEST)) {
1616 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1617 "DIGEST46:3DES key input: ", input);
1618 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1619 "DIGEST47:3DES key ede: ", ede);
1620 traceOutput(DP_CLASS_NAME, "makeDesKeys",
1621 "DIGEST48:3DES key material: ",
1622 ((DESedeKeySpec)spec).getKey());
1623 logger.log(Level.FINEST, "DIGEST49: is parity-adjusted? ",
1624 Boolean.valueOf(DESedeKeySpec.isParityAdjusted(ede, 0)));
1625 }
1626 } else {
1627 throw new IllegalArgumentException("Invalid DES strength:" +
1628 desStrength);
1629 }
1630 return desFactory.generateSecret(spec);
1631 }
1632 }